#include <iostream>
#include <cstdlib>

#include "boost/asio/io_context.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/write.hpp"
#include "boost/asio/read.hpp"
#include "boost/asio/connect.hpp"
#include "boost/asio/buffer.hpp"
#include "boost/asio/ssl.hpp"


using boost::asio::ip::tcp;
namespace ssl = boost::asio::ssl;

const int BUF_SIZE = 1024;

class Client{
public:
    Client(boost::asio::io_context& io_context,
           ssl::context& ssl_context,
           tcp::resolver::results_type endpoints)
        : socket_(io_context, ssl_context){

        socket_.set_verify_mode(ssl::verify_peer);
        socket_.set_verify_callback(std::bind(&Client::VerifyCertificate, this,
                                              std::placeholders::_1,
                                              std::placeholders::_2));

        boost::asio::async_connect(socket_.lowest_layer(), endpoints,
                                   std::bind(&Client::HandleConnect, this,
                                             std::placeholders::_1));
    }

    bool VerifyCertificate(bool preverified, ssl::verify_context& ctx){
        char subject_name[256];
        X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
        X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
        std::cout << "Verifying " << subject_name << "\n";

        return preverified;
    }

    void HandleConnect(boost::system::error_code ec){
        if(!ec){
            socket_.async_handshake(ssl::stream_base::client,
                                    std::bind(&Client::HandleHandshake, this,
                                              std::placeholders::_1));
        }else{
            std::cerr<<"Connect failed: "<<ec.message()<<std::endl;
        }
    }

    void HandleHandshake(boost::system::error_code ec){
        if(!ec){
            std::cout<<"Enter message: ";
            std::cin.getline(request_, BUF_SIZE);
            std::size_t request_length = strlen(request_);

            boost::asio::async_write(socket_,
                                     boost::asio::buffer(request_, request_length),
                                     std::bind(&Client::HandleWrite, this,
                                               std::placeholders::_1,
                                               std::placeholders::_2));
        }else{
            std::cerr<<"Handshake failed: "<<ec.message()<<std::endl;
        }
    }

    void HandleWrite(boost::system::error_code ec, std::size_t length){
        if(!ec){
            boost::asio::async_read(socket_,
                                    boost::asio::buffer(reply_, length),
                                    std::bind(&Client::HandleRead, this,
                                              std::placeholders::_1,
                                              std::placeholders::_2));
        }else{
            std::cerr<<"Write failed: "<<ec.message()<<std::endl;
        }
    }

    void HandleRead(boost::system::error_code ec, std::size_t length){
        if(!ec){
            std::cout<<"Reply: ";
            std::cout.write(reply_, length);
            std::cout<<std::endl;
        }else{
            std::cerr<<"Read failed: "<<ec.message()<<std::endl;
        }
    }
private:
    ssl::stream<tcp::socket> socket_;

    char request_[BUF_SIZE];
    char reply_[BUF_SIZE];
};

int main(int argc, char* argv[]) {
  if (argc != 3) {
    std::cout << "Usage: " << argv[0] << " <host> <port>" << std::endl;
    return 1;
  }

  try {
    boost::asio::io_context io_context;

    tcp::resolver resolver(io_context);
    auto endpoints = resolver.resolve(argv[1], argv[2]);

    ssl::context ssl_context(ssl::context::sslv23);

    // Use the default paths for finding CA certificates.
    //ssl_context.set_default_verify_paths();
    // TODO
    ssl_context.load_verify_file("server.crt");

    Client client(io_context, ssl_context, endpoints);

    io_context.run();

  } catch (const std::exception& e) {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}
